#ifndef __SC_ENGINE_H__
#define __SC_ENGINE_H__

////////////////////////////////////////////////////////////////////////////////
// Includes
////////////////////////////////////////////////////////////////////////////////

#include <string>
#include <vector>
#include "SCData.hpp"
#include "SComputationInfo.hpp"
#include "SCProgramState.hpp"
#include <iostream>
#include <deque>

#include "SC_LibConfig.h"

////////////////////////////////////////////////////////////////////////////////
// Namespaces
////////////////////////////////////////////////////////////////////////////////

namespace SC {

////////////////////////////////////////////////////////////////////////////////
// Forward declarations
////////////////////////////////////////////////////////////////////////////////

class SCSystem;
class SCProgram;

////////////////////////////////////////////////////////////////////////////////
// Implementation
////////////////////////////////////////////////////////////////////////////////

/**
 *  This class defines the "Systemic Computation" engine running a simulation.
 */
class LIBRARY_DECL SCEngine {

	public:
		
		/// Default constructor
		SCEngine();
		
		/// Destructor
		~SCEngine() {}

		/**
		 *	Iterates and returns true if a computation occurs.
		 *	For a computation to occur, a relevant context must be picked up.
		 *	A context system will be relevant if it can interact, i.e.
		 *	if it can reach two subject systems respectively matching the two
		 *	templates.
		 */
		const bool Iterate();
		
		/**
		 *	Executes a recorded computation from the given stream.
		 *	The read computation should be a valid computation coming from a
		 *	log input recorded during a previous computation. The method returns
		 *	true upon success, false otherwise.
		 */
		const bool Execute(std::istream &);
		
		/// Initiliase data
		void InitData(const SCProgram &, const std::vector<std::string> &);
		
		/// Returns the number of systems
		const unsigned int GetNbSystems() const { return this->data.GetNbSystems(); }

		/// Returns the ith system in read-only mode
		const SCSystem & GetSystem(const unsigned int i) const { return this->data.GetSystem(i); }
		
		/// Returns the systems in read-only mode
		const std::vector<SCSystem> & GetSystems() const { return this->data.GetSystems(); }

		/// Returns the universe (read-only)
		const SCSystem & GetUniverse() const { return this->data.GetUniverse(); }

		/// Returns the universe
		SCSystem & Universe() { return this->data.Universe(); }

		/// Returns the contexts in read-only mode
		const std::vector<SCSystem*> & GetContexts() const { return this->data.GetContexts(); }

		/// Clear
		void Clear();

		/// Returns the number of elapsed iterations
		const unsigned int GetNbIterations() const { return this->nb_iterations; }
		
		/// Returns the number of elapsed computations
		const unsigned int GetNbComputations() const { return this->nb_computations; }

		/// Set the recording level
		void SetRecordingLevel(const unsigned int value) { this->recording_level = (value<2)?value:2; }
		
		/// Returns the recording level
		const unsigned int GetRecordingLevel() const { return this->recording_level; }

		/// Get the last recorded computation (updated each computation)
		const SComputationInfo & GetLastComputation() const { return this->last_computation; }
		
		/// Returns true if the given string is valid
		const bool IsValidString(const std::string &) const;

		/// Write the last computation to the given output stream
		void WriteLastComputation(std::ostream &);
		
		/// Returns the number of memorised states
		const unsigned int GetNbMemorisedStates() const { return this->memorised_states.size(); }
		
		/// Returns the maximum number of memorised states
		const unsigned int GetMaxNbMemorisedStates() const { return this->max_nb_memorised_states; }
		
		/// Sets the maximum number of memorised states
		void SetMaxNbMemorisedStates(const unsigned int);
		
		/// Restore the required state
		const bool RestoreState(unsigned int = 1);

	protected:

		/// Simulation data
		SCData data;

		/// Returns true if the descriptor string matches the target string
		const bool match(const std::string &, const std::string &);

		/// Apply the computation between the two subject within the context
		const bool compute(SCSystem &, SCSystem &, SCSystem &);
		
		/// Find the changes in a system from an old state to a new state
		void find_system_changes(const SCSystem &, const SCSystem &, SCSystemChanges &);
		
		/// Write the changes in a system to the given stream
		void write_system_changes(const SCSystemChanges &, std::ostream &);
		
		/// Read the last computation from the given input stream
		const bool read_last_computation(std::istream &);
		
		/// Read the changes in a system from the given stream
		void read_system_changes(SCSystemChanges &, std::istream &);
		
		/// Context systems with priority to be chosen
		std::vector<SCSystem *> pr_contexts;

		/// Function offset first bit and number of bits
		int func[2];
		
		/// Number of elapsed iterations
		unsigned int nb_iterations;
		
		/// Number of elapsed computations
		unsigned int nb_computations;
		
		/// Recording level [0,1,2]
		/// 0: records nothing
		/// 1: records the involved systems indices
		/// 2: records the involved systems indices and changes
		unsigned int recording_level;
		
		/// Last recorded computation
		SComputationInfo last_computation;
		
		/// Maximum number of memorised past states
		unsigned int max_nb_memorised_states;
		
		/// Memorised past states
		std::deque<SCProgramState> memorised_states;
		
		/// Memorise the current state
		void memorise_state();

};

////////////////////////////////////////////////////////////////////////////////
// Closing Namespaces
////////////////////////////////////////////////////////////////////////////////

}

////////////////////////////////////////////////////////////////////////////////

#endif
